{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# E2E Approach -- Scenario II\n",
    "\n",
    "In this notebook, we report the code related to the *hybrid* appraoch in our paper [[1]](#ourpaper). We consider the delay caused by the feedback loop used by the mobile user equipment to send to the base station the channel state information (CSI). Due to this feedback delay, the CSI available at the base station becomes outdated. In our paper, we present an E2E appraoch, which takes in input the channel history available at the base station and maps it to the probability of an error event for all the modulation and coding schemes (MCSs) the base station can select. The error event corresponds to an unsuccessfully decoded frame at the receiver. If the error event probabilities are known for all the MCSs, then it is straightforward to select the MCS that maximizes the spectral efficiency.\n",
    "\n",
    "Under Scenario II, we build a differnt neural network for each delay, but we train it to work on the full range of dopplers and signal-to-noise ratios.\n",
    "\n",
    "It must be noted that the training datasets listed below in the code are currently not available in the repository due to space limitations. The **training datasets can be found at**: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*. At any rate, in the repository, the reader can also find the codes in *radio_data* folder which can be run to generate the datasets. \n",
    "\n",
    "**Note**: the training might take some hours, depending on the available computational resources, the dimension of the training set, the dimension of the network, and the number of epochs. \n",
    "\n",
    "<a id='ourpaper'></a> [1] \"Wireless link adaptation - a hybrid data-driven and model-based approach\", Lissy Pellaco, Vidit Saxena, Mats Bengtsson, Joakim Jaldén. Submitted to SPAWC 2020."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Import libraries and utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import utilities as utils\n",
    "from keras.optimizers import Adam\n",
    "from keras.backend.tensorflow_backend import set_session\n",
    "from keras.backend import clear_session\n",
    "import tensorflow as tf\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Maximum feedback delay that we consider\n",
    "maximum_delay = 9\n",
    "\n",
    "# Number of past CSI stored at the base station and used as input to the neural network\n",
    "ANN_MEMORY = 10\n",
    "# Flag used to indicate if the channel is noisy\n",
    "CHANNEL_EST_NOISE = True\n",
    "\n",
    "# Parameters related to neural network training\n",
    "TRAINING_FRACTION = 0.2\n",
    "BATCH_SIZE = 32\n",
    "NROF_EPOCS = 20 \n",
    "\n",
    "# Flag to indicate if the trained models should be saved\n",
    "save_model = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load the Dataset\n",
    "\n",
    "The channel dataset is a dict with the following keys :  \n",
    " - 'channel'\n",
    "     - Complex channel coefficients \n",
    "     - Numpy array [ NROF_FRAMES x NROF_SUBCARRIERS x NROF_SNRS]\n",
    " - 'block_success'\n",
    "      - Binary success events (ACKs)\n",
    "      - Numpy array [ NROF_FRAMES x NROF_MCS x NROF_SNRS]\n",
    " - 'snrs_db '      \n",
    "     - Evaluated average SNR values\n",
    "     - Numpy array [ NROF_SNRS ]\n",
    " - 'block_sizes'\n",
    "     - Evaluated transport block sizes\n",
    "     - Numpy array [ NROF_MCS ]\n",
    "     \n",
    "The name of the dataset, e.g., ITU_VEHICULAR_B_5000_60kmph, is to be interpreted in this way: \n",
    " - channel model (ITU_VEHICULAR_B)\n",
    " - number of channel realizations (5000)\n",
    " - relative velocity between the base station and the user mobile equipment (60kmph)\n",
    " \n",
    "The **training datasets can be found at**: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The files stored in the file_set ARE NOT in the repository due to space limitations.\n",
    "# The training datasets can be found at: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*\n",
    "# The reader has also access to the \"radio_data/Generate_Data.ipynb\" which we used to generate the training datasets.\n",
    "# N.B. a small training set with new samples at snr of 5dB is added to boost the performance at low snr\n",
    "FADING_CHANNEL_DATAFILES = []\n",
    "\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_30kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_45kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_60kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_75kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_90kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_105kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_120kmph.npy')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data Preprocessing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "channel_coeff  = []\n",
    "block_success  = []\n",
    "\n",
    "for file in FADING_CHANNEL_DATAFILES:\n",
    "    DATASET = np.load( file, allow_pickle = True )[()]\n",
    "    \n",
    "    nrof_train_samples = int( TRAINING_FRACTION * DATASET['channel'].shape[0] )\n",
    "    coeff = utils.calculate_channel_coefficients_scaled( DATASET['channel'][ :nrof_train_samples, :, : ],\n",
    "                                                         DATASET['snrs_db'],\n",
    "                                                         channel_estimation_noise = CHANNEL_EST_NOISE )\n",
    "        \n",
    "    channel_coeff.append( coeff )\n",
    "    block_success.append( DATASET['block_success'][ :nrof_train_samples, :, : ] )\n",
    "    \n",
    "channel_coeff = np.vstack( channel_coeff )\n",
    "block_success = np.vstack( block_success )\n",
    "\n",
    "NROF_FRAMES, NROF_SUBCARRIERS, NROF_SNRS = channel_coeff.shape\n",
    "NROF_MCS = block_success.shape[ 1 ]\n",
    "\n",
    "channel_coeff_concat = np.concatenate( ( np.real( channel_coeff ), np.imag( channel_coeff ) ), axis = 1 )\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Build the Neural Network model, define the optimizer, and the cost function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_ann_model():\n",
    "    from keras.models import Sequential\n",
    "    from keras.layers import Dense, Dropout\n",
    "\n",
    "    model = Sequential()\n",
    "    model.add( Dense( 1024, \n",
    "                      input_dim = NROF_SUBCARRIERS * ANN_MEMORY * 2, \n",
    "                      kernel_initializer='normal', \n",
    "                      activation='relu' ) )\n",
    "\n",
    "    model.add( Dense( 512, \n",
    "                      kernel_initializer = 'normal', \n",
    "                      activation='relu' ) )\n",
    "\n",
    "    model.add( Dense( 1024, \n",
    "                      kernel_initializer = 'normal', \n",
    "                      activation='relu' ) )\n",
    "    \n",
    "    model.add( Dense( NROF_MCS, \n",
    "                      kernel_initializer='normal', \n",
    "                      activation='sigmoid' ) )\n",
    "\n",
    "    # Compile model\n",
    "    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # for binary classification\n",
    "\n",
    "    \n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train the Neural Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for delay in range(0,maximum_delay + 1):\n",
    "    \n",
    "    channel_coeff  = []\n",
    "    block_success  = []\n",
    "\n",
    "    # Extract training data from all datasets, excluding the last one (used later)\n",
    "    for file in FADING_CHANNEL_DATAFILES[:-1]:\n",
    "        DATASET = np.load( file, allow_pickle = True )[()]\n",
    "\n",
    "        nrof_train_samples = int( TRAINING_FRACTION * DATASET['channel'].shape[0] )\n",
    "        coeff = utils.calculate_channel_coefficients_scaled( DATASET['channel'][ :nrof_train_samples, :, : ],\n",
    "                                                             DATASET['snrs_db'],\n",
    "                                                             channel_estimation_noise = CHANNEL_EST_NOISE )\n",
    "\n",
    "        channel_coeff.append( coeff )\n",
    "        block_success.append( DATASET['block_success'][ :nrof_train_samples, :, : ] )\n",
    "    \n",
    "    channel_coeff = np.vstack( channel_coeff )\n",
    "    block_success = np.vstack( block_success )\n",
    "\n",
    "    NROF_FRAMES, NROF_SUBCARRIERS, NROF_SNRS = channel_coeff.shape\n",
    "    NROF_MCS = block_success.shape[ 1 ]\n",
    "\n",
    "    channel_coeff_concat = np.concatenate( ( np.real( channel_coeff ), np.imag( channel_coeff ) ), axis = 1 )\n",
    "\n",
    "    config = tf.ConfigProto()\n",
    "    config.gpu_options.allow_growth = True  \n",
    "\n",
    "    sess = tf.Session( config = config )\n",
    "    set_session(sess) \n",
    "    if delay > 0:\n",
    "        train_input  = utils.flatten_snr_axis( channel_coeff_concat[ :-delay, :, :] )\n",
    "    else:\n",
    "        train_input  = utils.flatten_snr_axis( channel_coeff_concat )\n",
    "\n",
    "    train_target = utils.flatten_snr_axis( block_success [ delay :, :, : ] )\n",
    "\n",
    "    train_input, train_target = utils.shuffle_data( train_input, train_target )\n",
    "\n",
    "    model = create_ann_model( )\n",
    "    \n",
    "    # Storing current time \n",
    "    start = time.time()\n",
    "\n",
    "    history = model.fit( utils.stack_features( train_input, ANN_MEMORY ), \n",
    "                         train_target, \n",
    "                         batch_size = BATCH_SIZE, \n",
    "                         epochs     = NROF_EPOCS, \n",
    "                         validation_split = 0.1, \n",
    "                         verbose    = 1 ) # the \"verbose parameter\" can be changed to display more about the training progess of each epoch\n",
    "    channel_coeff  = []\n",
    "    block_success  = []\n",
    "\n",
    "    # Extract training data from last dataset\n",
    "    for file in [FADING_CHANNEL_DATAFILES[-1]]:\n",
    "        DATASET = np.load( file, allow_pickle = True )[()]\n",
    "\n",
    "        nrof_train_samples = int( TRAINING_FRACTION * DATASET['channel'].shape[0] )\n",
    "        coeff = utils.calculate_channel_coefficients_scaled( DATASET['channel'][ :nrof_train_samples, :, : ],\n",
    "                                                             DATASET['snrs_db'],\n",
    "                                                             channel_estimation_noise = CHANNEL_EST_NOISE )\n",
    "\n",
    "        channel_coeff.append( coeff )\n",
    "        block_success.append( DATASET['block_success'][ :nrof_train_samples, :, : ] )\n",
    "    \n",
    "    channel_coeff = np.vstack( channel_coeff )\n",
    "    block_success = np.vstack( block_success )\n",
    "\n",
    "    channel_coeff_concat = np.concatenate( ( np.real( channel_coeff ), np.imag( channel_coeff ) ), axis = 1 )\n",
    "\n",
    "    if delay > 0:\n",
    "        train_input  = utils.flatten_snr_axis( channel_coeff_concat[ :-delay, :, :] )\n",
    "    else:\n",
    "        train_input  = utils.flatten_snr_axis( channel_coeff_concat )\n",
    "\n",
    "    train_target = utils.flatten_snr_axis( block_success [ delay :, :, : ] )\n",
    "\n",
    "    train_input, train_target = utils.shuffle_data( train_input, train_target )\n",
    "    \n",
    "    # Additional training\n",
    "    history = model.fit( utils.stack_features( train_input, ANN_MEMORY ), \n",
    "                         train_target, \n",
    "                         batch_size = BATCH_SIZE, \n",
    "                         epochs     = NROF_EPOCS, \n",
    "                         validation_split = 0.1, \n",
    "                         verbose    = 1 ) # the \"verbose parameter\" can be changed to display more about the training progess of each epoch\n",
    "\n",
    "        \n",
    "    file = 'Trained_models_ScenarioII/ANN_E2E_MCS_PRED_DELAY_%d_MEM_10_SNR.h5'%( delay )\n",
    "    print(\"Training the neural network took:\",time.time()-start)\n",
    "    if save_model == True:\n",
    "        model.save( file )\n",
    "        print( 'Saved model to %s'%( file ) )\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
